﻿using System;
using System.Collections.Generic;
using System.Text;
using System.IO;

namespace DotNetMigrations
{
	public class MigrationScript
	{
		/// <summary>
		/// Generate a new migration script 
		/// with the specified name.
		/// </summary>
		public static bool Generate(string migrationName)
		{
			try
			{
				if (!Directory.Exists(Program.MigrationDir))
					Directory.CreateDirectory(Program.MigrationDir);

				string file = CreateNewMigration(migrationName);
				Output.Write("Migration " + file + " created");
				return true;
			}
			catch (Exception ex)
			{
				Output.Warn(ex.Message);
				return false;
			}
		}

		/// <summary>
		/// Get the latest version that exists in the
		/// migrations script directory.
		/// </summary>
		public static long GetLatestMigrationVersion()
		{
			if (!Directory.Exists(Program.MigrationDir))
				return 0;

			string[] files = Directory.GetFiles(Program.MigrationDir, "*.sql");
			long latestVersion = 0;

			foreach (string file in files)
			{
				string fileName = Path.GetFileName(file);
				string sVers = fileName.Split('_')[0];
				long iVers;
				if (long.TryParse(sVers, out iVers))
				{
					latestVersion = Math.Max(latestVersion, iVers);
				}
			}

			return latestVersion;
		}

		/// <summary>
		/// Get all teardown scripts needed to revert the current version
		/// of the schema to the target version.
		/// </summary>
		public static List<string> GetTearDownScripts(long currentVersion, long targetVersion)
		{
            List<long> fileVersions = GetScriptFiles();

            fileVersions.Sort(delegate(long l1, long l2) { return l2.CompareTo(l1); });

            if (fileVersions == null)
            {
                // no scripts were found, error
                return null;
            }

            List<string> scripts = new List<string>();

            foreach (long fileVersion in fileVersions)
            {
                if (fileVersion <= currentVersion && fileVersion > targetVersion)
                {
                    List<string> sql = GetScript("TEARDOWN", fileVersion);
                    if (sql == null)
                    {
                        //	no teardown script was found for this migration, ERROR
                        return null;
                    }
                    scripts.AddRange(sql);
                }
            }

            return scripts;
		}

		/// <summary>
		/// Get all setup scripts needed to upgrade the current version
		/// of the schema to the target version.
		/// </summary>
		public static List<string> GetSetupScripts(long currentVersion, long targetVersion)
		{
		    List<long> fileVersions = GetScriptFiles();

            if (fileVersions == null)
            {
                // no scripts were found, error
                return null;
            }

			List<string> scripts = new List<string>();

            foreach (long fileVersion in fileVersions)
		    {
		        if (fileVersion > currentVersion && fileVersion <= targetVersion)
		        {
                    List<string> sql = GetScript("SETUP", fileVersion);
                    if (sql == null)
                    {
                        //	no setup script was found for this migration, ERROR
                        return null;
                    }
                    scripts.AddRange(sql);
		        }
		    }

			return scripts;
		}

        /// <summary>
        /// Retrieves a list of all version numbers based on the available scripts.
        /// </summary>
        /// <returns>A list of version numbers based on the scripts in the migration directory.</returns>
        private static List<long> GetScriptFiles()
        {
            if (!Directory.Exists(Program.MigrationDir))
                return null;

            List<long> fileVersions = new List<long>();
            string[] files = Directory.GetFiles(Program.MigrationDir, "*.sql");
            
            foreach(string file in files)
            {
                string fileName = Path.GetFileName(file);
                string sVers = fileName.Split('_')[0];
                long iVers;
                if (long.TryParse(sVers, out iVers))
                {
                    fileVersions.Add(iVers);
                }
            }

            fileVersions.Sort();

            return fileVersions;
        }

		/// <summary>
		/// Gets a SETUP or TEARDOWN script for a specific migration.
		/// </summary>
		private static List<string> GetScript(string stepName, long version)
		{
			string[] files = Directory.GetFiles(Program.MigrationDir, version.ToString("000") + "_*.sql");
			if (files.GetLength(0) > 0)
			{
				StringBuilder script = new StringBuilder();
				using (StreamReader reader = File.OpenText(files[0]))
				{
					string line = reader.ReadLine();
					bool started = false;
					while (line != null)
					{
						if (started)
						{
							if (line == "END_" + stepName + ":")
								break;

							//	split by the "GO" keyword
							//	each item in the split will be executed as a
							//	seperate sql command
							if (line.Trim().ToUpper() == "GO")
								script.Append("|");	//	we'll split by this character later
							else
								script.AppendLine(line);
						}
						else
						{
							if (line == "BEGIN_" + stepName + ":")
								started = true;
						}
						line = reader.ReadLine();
					}
				}
				if (script.ToString().Trim().Length == 0)
				{
					Output.Warn(stepName + " not found for migration version " + version);
					return null;
				}

				List<string> scriptSplitByGO = new List<string>();
				scriptSplitByGO.AddRange(script.ToString().Split('|'));

				return scriptSplitByGO;
			}
			else
			{
				Output.Warn("Migration not found for version " + version);
				return null;
			}
		}

		/// <summary>
		/// Creates a new migration script with the
		/// specified name and version number based on
		/// the time of generation.
		/// </summary>
		/// <returns>
		/// File name of new migration script.
		/// </returns>
		private static string CreateNewMigration(string name)
		{
			string file = Path.Combine(Program.MigrationDir, DateTime.Now.ToString("yyyyMMddhhmmss") + "_" + name + ".sql");

			using (StreamWriter writer = File.CreateText(file))
			{
				writer.WriteLine("BEGIN_SETUP:\r\n\r\n\r\n");
				writer.WriteLine("END_SETUP:");
				writer.WriteLine("BEGIN_TEARDOWN:\r\n\r\n\r\n");
				writer.WriteLine("END_TEARDOWN:");

				writer.Close();
			}

			return Path.GetFileNameWithoutExtension(file);
		}

        /// <summary>
        /// Retrieves the version number previous to the current one.
        /// </summary>
        /// <returns>The previous version number.</returns>
        public static long GetPreviousMigrationVersion()
        {
            long currentVersion = SchemaInfoTable.GetSchemaVersion();

            List<long> versions = GetScriptFiles();
            long previousVersion = 0;

            foreach(long version in versions)
            {
                if (version == currentVersion)
                {
                    break;
                }

                previousVersion = version;
            }

            return previousVersion;
        }
	}
}
